#include "MulticastSender.h"

#include <arpa/inet.h>
#include <cstring>
#include <iostream>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>

/**
 * @brief 构造函数
 * @param groupAddr 组播地址
 * @param groupPort 组播端口
 * @param parent 父对象
 */
MulticastSender::MulticastSender(const std::string& groupAddr, u16 groupPort, QObject* parent)
    : QObject(parent)
    , sockfd_(-1)               // 初始化套接字描述符为无效
    , groupAddr_(groupAddr)     // 设置组播地址
    , groupPort_(groupPort)     // 设置组播端口
    , serverAddr_()             // 初始化服务器地址结构
{
}

/**
 * @brief 析构函数
 *
 * 关闭套接字，释放资源。
 */
MulticastSender::~MulticastSender()
{
    if (sockfd_ != -1)
    {
        close(sockfd_);     // 关闭套接字
    }
}

/**
 * @brief 初始化发送套接字
 * @param localInterface 本地网络接口名称
 * @return 初始化是否成功
 */
bool MulticastSender::initSocket(const std::string& localInterface)
{
    // 创建一个 UDP 套接字用于发送数据
    sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd_ < 0)
    {
        perror("Send socket creation failed");
        return false;
    }

    // 清空服务器地址结构
    memset(&serverAddr_, 0, sizeof(serverAddr_));
    serverAddr_.sin_family      = AF_INET;                       // 设置地址族为 IPv4
    serverAddr_.sin_addr.s_addr = inet_addr(groupAddr_.c_str()); // 组播地址
    serverAddr_.sin_port        = htons(groupPort_);             // 组播端口

    // 设置套接字选项，禁用组播回环（不接收自己发送的组播消息）
    s32 loop = 0;
    if (setsockopt(sockfd_, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)) < 0)
    {
        perror("Setsockopt failed for multicast LOOP");     // 打印错误信息
        close(sockfd_);                                     // 关闭套接字
        sockfd_ = -1;                                       // 重置套接字描述符
        return false;
    }

    // 设置组播 TTL（Time To Live）为 255，控制数据包在网络中的传播范围
    s32 ttl = 255;
    if (setsockopt(sockfd_, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0)
    {
        perror("Setsockopt failed for multicast TTL");      // 打印错误信息
        close(sockfd_);                                     // 关闭套接字
        sockfd_ = -1;                                       // 重置套接字描述符
        return false;
    }

    // 获取本地网卡的IP地址
    std::string localIP = getInterfaceIP(localInterface);
    if (localIP.empty())
    {
        std::cerr << "Failed to get IP address for interface " << localInterface << std::endl;
        close(sockfd_);                                 // 关闭套接字
        sockfd_ = -1;                                   // 重置套接字描述符
        return false;
    }

    // 将获取到的本地 IP 地址转换为 in_addr 结构
    struct in_addr localInterfaceAddr;
    localInterfaceAddr.s_addr = inet_addr(localIP.c_str());

    // 设置 IP_MULTICAST_IF 选项，指定发送数据的网络接口(网卡)
    if (setsockopt(sockfd_, IPPROTO_IP, IP_MULTICAST_IF, &localInterfaceAddr, sizeof(localInterfaceAddr)) < 0)
    {
        perror("Setsockopt failed for multicast interface");
        close(sockfd_);                                         // 关闭套接字
        sockfd_ = -1;                                           // 重置套接字描述符
        return false;
    }

    return true;        // 初始化成功
}

/**
 * @brief 发送组播消息
 * @param message 要发送的消息
 * @return 发送是否成功
 */
bool MulticastSender::sendToMulticastGroup(const std::string& message)
{
    if (sockfd_ == -1)
    {
        std::cerr << "Socket not initialized" << std::endl;     // 如果套接字未初始化，输出错误信息
        return false;
    }

    // 发送消息到组播组
    s32l len = sendto(sockfd_, message.c_str(), message.size(), 0, reinterpret_cast<struct sockaddr*>(&serverAddr_), sizeof(serverAddr_));
    if (len < 0)
    {
        perror("Send to multicast group failed");
        return false;
    }

    std::cout << "Message sent to multicast group: " << message << std::endl;       // 输出发送成功信息
    return true; // 发送成功
}

/**
 * @brief 获取指定网络接口的 IP 地址
 * @param interface 网络接口名称
 * @return 接口的 IP 地址字符串，失败返回空字符串
 *
 * 使用 ioctl 系统调用获取网络接口的 IP 地址。
 */
std::string MulticastSender::getInterfaceIP(const std::string& interface)
{
    /*  创建一个临时 UDP 套接字。
        使用 ifreq 指定网络接口名称。
        通过 ioctl 获取接口的 IPv4 地址。
        将地址从二进制转换为字符串格式。
        关闭套接字，释放资源。
        返回 IP 地址。
    */

    s32                 tempSockfd;
    struct ifreq        ifr;
    struct sockaddr_in* addr;

    // 创建一个临时的 UDP 套接字用于获取接口信息
    tempSockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (tempSockfd == -1)
    {
        perror("Socket creation failed");
        return "";
    }

    // 清空 ifreq 结构体
    std::memset(&ifr, 0, sizeof(ifr));
    std::strncpy(ifr.ifr_name, interface.c_str(), IFNAMSIZ - 1);        // 设置接口名称

    // 获取接口的IP地址
    if (ioctl(tempSockfd, SIOCGIFADDR, &ifr) == -1)
    {
        perror("Failed to get IP address");
        close(tempSockfd);      // 关闭套接字
        return "";
    }

    // 将获取到的地址转换为 sockaddr_in 结构体
    addr           = reinterpret_cast<struct sockaddr_in*>(&ifr.ifr_addr);
    std::string ip = inet_ntoa(addr->sin_addr);     // 将地址转换为字符串
    close(tempSockfd);      // 关闭临时套接字
    return ip;
}
